d3.path
用途就是用來繪製複雜的path
路徑。d3.path
會回傳一個實現canvas
路徑的物件並序列化成svg
路徑資料。
範例:
let canvas = document.getElementById('target');
let context = canvas.getContext('2d');
function draw(context) {
context.moveTo(10, 10); // 移動到(10, 10)
context.lineTo(100, 10); // 連線至(100, 10)
context.arcTo(150, 150, 300, 10, 40); // 繪製一弧形。
context.lineTo(300, 10); // 連線至 ⟨300,10⟩
return context;
}
context.beginPath();
draw(context);
context.stroke();
而如果我們將d3.path
放入function draw
,根據敘述,d3.path
就是支援canvas context
的繪製方法。
console.log(draw(d3.path()).toString());
符合期待,d3.path
是支援canvas
繪製path
的方法,並會序列化成svg
路徑並。
既然已經有辦法產生路徑,試試看在svg
上吧!
d3.select("svg")
.call(svg => svg.append("path")
.style("stroke", "black")
.style("fill", "none")
.attr("d", draw(d3.path())))
.node()
與canvas
結果相同!
path
針對資料變化產生的漸變有些特別,我們先來一個範例。
範例
// 宣告基本sv圖層與設定
const width = 1200;
const height = 600;
const padding = 30;
const innerWidth = width - padding;
const innerHeight = height - padding;
const svg = d3.select('svg').attr('width', width).attr('height', height);
const rootLayer = svg.append('g').attr('transform', `translate(${padding}, ${padding})`)
const axisLayer = rootLayer.append('g');
const xAxisLayer = axisLayer.append('g');
const yAxisLayer = axisLayer.append('g');
const lineLayer = rootLayer.append('g');
let path = lineLayer.append('path');
// 資料源
let datas = []
// 繪製path的部分
const drawLine = (ctx, datas, xScale, yScale) => {
datas.forEach((data, index) => {
// 如果是0,代表是第一個資料,不需要LINETO
if (index !== 0) {
ctx.lineTo(xScale(data.time), yScale(data.value));
}
ctx.moveTo(xScale(data.time), yScale(data.value))
});
return ctx
}
// 繪製方法
const draw = (datas) => {
let xExtent = d3.extent(datas, data => new Date(data.time));
let yExtent = d3.extent(datas, data => data.value);
let xScale = d3.scaleTime().range([0, innerWidth]).domain(xExtent);
let yScale = d3.scaleLinear().range([0, innerHeight]).domain(yExtent);
let xAxis = d3.axisBottom(xScale).tickFormat( d3.timeFormat("%H:%M:%S"));
let yAxis = d3.axisLeft(yScale);
xAxisLayer.call(xAxis);
yAxisLayer.call(yAxis);
// 產生PATH路徑的部分
let linePath = drawLine(d3.path(), datas, xScale, yScale);
// 將產生的路徑設定置PATH元件上。
path
.attr('fill', 'none')
.attr('stroke', 'black')
.transition()
.attr('d', linePath.toString());
}
// 自動產生一堆資料
const autoGen = () => {
datas.push({
value: Math.round(Math.random() * 100),
time: new Date()
})
}
setInterval(() => {
autoGen();
// 超過20筆資料就去掉第一個
if (datas.length > 20) {
datas = datas.slice(1)
}
draw(datas)
}, 200);
有發現問題,與之前d3.selection data
的問題一樣,其實線段應該要水平移動,並非垂直更動數值,但由於目前已經產生為line path
一串路徑字串,其實d3
確實沒辦法知道到底該如何變化。
至於說要如何克服此問題,就必須透過其他平移技巧。
不想自己寫繪製部分,還可以使用d3.line
,不過可以了解path
與canvas
的繪製方法,不覺得挺有趣的嗎?